/**
 * File: FileIndexReaderImpl.java
 * Created by: mhaimel
 * Created on: 15 Jul 2009
 * CVS:  $Id: FileIndexReaderImpl.java,v 1.3 2009/12/07 10:14:40 mhaimel Exp $
 */
package uk.ac.ebi.curtain.util.impl;

import static uk.ac.ebi.curtain.util.FileIndexUtil.CONTIG_HEADER;
import static uk.ac.ebi.curtain.util.FileIndexUtil.CONTIG_POS_HEADER;
import static uk.ac.ebi.curtain.util.FileIndexUtil.FILE_HEADER;
import static uk.ac.ebi.curtain.util.FileIndexUtil.INSLEN_HEADER;
import static uk.ac.ebi.curtain.util.FileIndexUtil.INS_SD_HEADER;
import static uk.ac.ebi.curtain.util.FileIndexUtil.READ_HEADER;
import static uk.ac.ebi.curtain.util.FileIndexUtil.READ_POS_HEADER;
import static uk.ac.ebi.curtain.util.FileIndexUtil.SEP_CHAR;
import static uk.ac.ebi.curtain.util.FileIndexUtil.SEP_CHAR_Str;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import uk.ac.ebi.curtain.util.FileIndexReader;
import uk.ac.ebi.curtain.util.FileIndexUtil.FileIndexEvent;
import uk.ac.ebi.curtain.utils.CurtainUncheckedException;
import uk.ac.ebi.curtain.utils.data.FileType;
import uk.ac.ebi.curtain.utils.data.ReadType;
import uk.ac.ebi.curtain.utils.file.FileInfo;
import uk.ac.ebi.curtain.utils.io.impl.FileIO;
import uk.ac.ebi.velvet.model.Strand;

/**
 * @author mhaimel
 *
 */
public class FileIndexReaderImpl implements FileIndexReader {
	private final Log log = LogFactory.getLog(this.getClass());
	
	private final File idxFile;
	private FileIndexEvent eventHandler;
	private IndexFilter filter = null;
	private BufferedReader in;
	private Map<Integer, FileInfo> infoIdx = new HashMap<Integer, FileInfo>();

	public FileIndexReaderImpl(File idxFile) {
		this.idxFile = idxFile;
	}
	
	public void registerFileId(Integer fileId, FileInfo info) {
		infoIdx.put(fileId, info);
	}

	public void open() throws IOException{
		if(null == in){
			if(_getIdxFile().exists()){
				in = new FileIO(_getIdxFile()).getBufferedReader();
			} else {
				in = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(new byte[]{})));
			}
		}
	}

	public void close() throws IOException{
		_close();
	}
	
	public void readAll() throws IOException{
		_ensureOpen();
		_parse();
	}
	
	/**
	 * 
	 * @param nLines N number of lines
	 * @return boolean File reached end
	 * @throws IOException
	 */
	public boolean readBlock(Integer nLines) throws IOException{
		_ensureOpen();
		return _parse(nLines);		
	}
	
	private void _ensureOpen() throws IOException{
		if(null == in){
			throw new IOException("Input is not open for file " + _getIdxFile());
		}
	}
	
	public void _close(){
		IOUtils.closeQuietly(in);
		FileIO.closeQuietly(this.eventHandler);
		in = null;
	}

	private boolean _parse() throws IOException {
		String line = StringUtils.EMPTY;
		while((line = in.readLine()) != null && !isForceShutdown()){
			if(StringUtils.isNotBlank(line)){
				_handleLine(line);
			}
		}
		return line == null;
	}
	
	private boolean isForceShutdown() {
		return Thread.currentThread().isInterrupted();
	}

	private boolean _parse(Integer nLines) throws IOException {
		String line = StringUtils.EMPTY;
		int iCnt = 0;
		while(iCnt < nLines && (line = in.readLine()) != null){
			++iCnt;
			if(StringUtils.isNotBlank(line)){
				_handleLine(line);
			}
		}
		return line == null;
	}

	private void _handleLine(String line) {
		char header = line.charAt(0);
		switch (header) {
		case READ_HEADER:
			if(_getFilter().isReadAllow()){
				parseRead(line);
			}
			break;
		case READ_POS_HEADER:
			if(_getFilter().isReadAllow()){
				parseReadPos(line);
			}
			break;
		case CONTIG_HEADER:
			if(_getFilter().isContigAllow()){
				parseContig(line);
			}
			break;
		case CONTIG_POS_HEADER:
			if(_getFilter().isContigAllow()){
				parseContigPos(line);
			}
			break;
		case FILE_HEADER:
			parseFile(line);
			break;
		case INSLEN_HEADER:
			if(_getFilter().isFileInfoAllow()){
				parseInsertLength(line);
			}
			break;
		case INS_SD_HEADER:
			if(_getFilter().isFileInfoAllow()){
				parseInsertLengthSd(line);
			}
			break;
		default:
			throw new CurtainUncheckedException("Unknown header "+ header + " in line: " + line);
		}
	}

	private void parseContigPos(String line) {
		String[] arr = _toArray(line);
		Integer fId = Integer.valueOf(arr[1]);
		Long pos = Long.valueOf(arr[2]);
		_getHandler().addContigPosition(getFileInfo(fId), pos);
	}

	private void parseReadPos(String line) {
		String[] arr = _toArray(line);
		Integer fId = Integer.valueOf(arr[1]);
		Long pos = Long.valueOf(arr[2]);
		String symStr = arr[3];
		Strand strand = parseStrand(symStr);
		_getHandler().addReadPosition(getFileInfo(fId), strand, pos);
	}

	private void parseContig(String line) {
		String[] arr = _toArray(line);
		// id, key, Strand.UNKOWN, pos
		String key = arr[1];
		Integer fId = Integer.valueOf(arr[3]);
		Long pos = Long.valueOf(arr[4]);

		_getHandler().addContig(getFileInfo(fId), key, pos);
	}

	private void parseRead(String line) {
		String[] arr = _toArray(line);
		String key = arr[1];
		String symStr = arr[2];
		Strand strand = parseStrand(symStr);
		Integer fId = Integer.valueOf(arr[3]);
		Long pos = Long.valueOf(arr[4]);
		_getHandler().addRead(getFileInfo(fId), key, strand, pos);
	}

	private Strand parseStrand(String symStr) {
		Strand strand = null;
		if(symStr.length() != 1){
			throw new CurtainUncheckedException("String invalid for Strand: " + symStr);
		} 
		strand = Strand.getStrand(symStr.charAt(0));
		if(null == strand){
			throw new CurtainUncheckedException("Not able to parse Strand from " + symStr);
		}
		return strand;
	}

	private FileInfo getFileInfo(Integer id) {
		FileInfo info = infoIdx.get(id);
		if(null == info){
			throw new CurtainUncheckedException("File info not yet registered with id " + id);
		}
		return info;
	}

	private void parseInsertLengthSd(String line) {
		String[] arr = _toArray(line);
		Integer cat = Integer.valueOf(arr[1]);
		Integer sd = Integer.valueOf(arr[2]);
		_getHandler().addInsertLengthSd(cat, sd);
	}

	private void parseInsertLength(String line) {
		String[] arr = _toArray(line);
		Integer cat = Integer.valueOf(arr[1]);
		Integer insLen = Integer.valueOf(arr[2]);
		_getHandler().addInsertLength(cat, insLen);
	}

	private void parseFile(String line) {
		String[] array = _toArray(line);
		/* 		cStr(FILE_HEADER), 
				id.toString(), 
				fi.getFile().toString(), 
				category.toString(),
				fi.getReadType().toString(), 
				fi.getFileType().toString() */

		Integer id = Integer.valueOf(array[1]);
		String file = array[2];
		Integer cat = Integer.valueOf(array[3]);
		ReadType rType = ReadType.valueOf(array[4]);
		FileType fType = FileType.valueOf(array[5]);
		FileInfo fi = new FileInfo(file,fType,rType);
		infoIdx.put(id, fi);

		if(_getFilter().isFileInfoAllow()){
			_getHandler().addFileId(id, fi);
			_getHandler().addFile(fi, cat);
		}
	}
	
	private FileIndexEvent _getHandler() {
		if(null == eventHandler){
			throw new CurtainUncheckedException("No Event handler registered!!");
		}
		return eventHandler;
	}
	
	public void setEventHandler(FileIndexEvent handler, IndexFilter filter) {
//		if(null != this.eventHandler){
//			throw new CurtainUncheckedException("Event handler already registered!!");
//		}
		this.eventHandler = handler;
		this.filter = filter;
	}
	public void setEventHandler(FileIndexEvent handler) {
		IndexFilter filter = new IndexFilter();
		filter.allowAll(true);
		setEventHandler(handler,filter);
	}
	private IndexFilter _getFilter() {
		return filter;
	}
	private File _getIdxFile() {
		return idxFile;
	}
	
	private String[] _toArraySplit(String line){
		return line.split(SEP_CHAR_Str);
	}

	private String[] _toArray(String line) {
		return _toArray(line,SEP_CHAR);
	}

	private String[] _toArray(String line, char c){
		ArrayList<String> arr = new ArrayList<String>();
		int prevPos = 0;
		int currPos = 0;
		
		while((currPos = line.indexOf(c, prevPos)) > 0){
			arr.add(line.substring(prevPos, currPos));
			currPos += 1; // jump char separator
			prevPos = currPos;
		}
		if(prevPos < line.length()){
			arr.add(line.substring(prevPos, line.length()));
		}
		return arr.toArray(new String[0]);
	}


}
